﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XenAPI;
using Alex.Library.Cloud.Libs;
using Alex.Library.Cloud.Libs.Exceptions;
using Alex.Library.Cloud.Citrix.VMMParameters;
using Alex.Library.Cloud.Citrix.ObjectReferences;

namespace Alex.Library.Cloud.Citrix
{
    /// <summary>
    /// Citrix Xen Server Api Provider
    /// </summary>
    public class CitrixApiProvider : IVMMProvider, IDisposable
    {
        public CitrixApiProvider(string hostName, int port, string userName, string password)
        {
            this.HostName = hostName;
            this.Port = port;
            this.UserName = userName;
            this.Password = password;
            try
            {
                PrimarySession = new Session(hostName, port);
                PrimarySession.login_with_password(UserName, Password);
            }
            catch(Exception e)
            {
                throw new VMMException("Login", e);
            }
        }

        public CitrixApiProvider(string url, string userName, string password)
        {
            Uri uri = new Uri(url);            
            if (!uri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) && 
                !uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
            {
                throw new VMMException("url must be start with http or https.");
            }
            this.HostName = uri.Host;
            this.Port = uri.Port;
            this.UserName = userName;
            this.Password = password;
            try
            {
                PrimarySession = new Session(HostName, Port);
                PrimarySession.login_with_password(UserName, Password);
            }
            catch (Exception e)
            {
                throw new VMMException("Login", e);
            }
        }

        protected Session PrimarySession { get; private set; }

        public string HostName { get; private set; }

        public int Port { get; private set; }

        public string UserName { get; private set; }

        public string Password { get; private set; }

        public string Version
        {
            get
            {
                return Host.get_API_version_major(PrimarySession, HostName).ToString();
            }
        }

        private readonly List<string> _capability = new List<string>
        {
            "Connect",
            "CreateVM",
            "StartVM",
        };

        public List<string> Capability
        {
            get { return _capability; }
        }

        public static Task get_task_record(Session session, string task_uuid)
        {
            return Task.get_record(session, task_uuid);
        }

        public static void destroy_task(Session session, string task_uuid)
        {
            Task.destroy(session, task_uuid);
        }

        public bool Connect(IVMMParameter arg, Action<object> action)
        {
            try
            {
                Session temp_session = new Session(this.PrimarySession, Session.STANDARD_TIMEOUT);
                temp_session.login_with_password(this.UserName, this.Password);
                if (action != null)
                {
                    action(temp_session);
                }
                temp_session.logout();
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool Connect(IVMMParameter arg)
        {
            return Connect(arg, null);
        }

        public bool CreateUser(IVMMParameter arg)
        {
            throw new VMMNotSupportedException("CreateUser");
        }

        public bool CreateVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is CreateVMParameter))
                {
                    throw new ArgumentException("arg is not CreateVMParameter type.");
                }
                CreateVMParameter carg = arg as CreateVMParameter;
                if (!carg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + carg.ErrorMessage);
                }                
                XenRef<VM> temp_ref = VM.get_by_uuid(PrimarySession, carg.template_uuid);
                
                if (carg.need_full_copy)
                {                                   
                    XenRef<SR> sr_ref = SR.get_by_uuid(PrimarySession, carg.sr_uuid);
                    carg.vmm_task_uuid = VM.async_copy(PrimarySession, temp_ref, 
                        carg.new_vm_name, sr_ref);                    
                }
                else
                {
                    carg.vmm_task_uuid = VM.async_clone(PrimarySession, temp_ref, carg.new_vm_name);
                }
                if (carg.vmm_task_uuid != null)
                {
                    task_status_type task_status = Task.get_status(PrimarySession, carg.vmm_task_uuid);
                    return task_status == task_status_type.pending || task_status == task_status_type.success;
                }
                return false;
            }
            catch (Exception e)
            {
                throw new VMMException("CreateVM", e);
            }
        }

        public bool StartVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is StartOrResumeParameter))
                {
                    throw new ArgumentException("arg is not StartOrResumeParameter type.");
                }
                StartOrResumeParameter sarg = arg as StartOrResumeParameter;
                if (!sarg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + sarg.ErrorMessage);
                }
                sarg.vmm_task_uuid = VM.async_start(PrimarySession, sarg.vm_uuid, sarg.paused, true);
                task_status_type task_status = Task.get_status(PrimarySession, sarg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("StartVM", e);
            }
        }

        public bool ShutdownGuest(IVMMParameter arg)
        {
            try
            {
                if (!(arg is VMOperationParameter))
                {
                    throw new ArgumentException("arg is not VMOperationParameter type.");
                }
                VMOperationParameter varg = arg as VMOperationParameter;
                if (!varg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + varg.ErrorMessage);
                }
                varg.vmm_task_uuid = VM.async_shutdown(PrimarySession, varg.vm_uuid);
                task_status_type task_status = Task.get_status(PrimarySession, varg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("ShutdownGuest", e);
            }
        }

        public bool RebootGuest(IVMMParameter arg)
        {
            try
            {
                if (!(arg is VMOperationParameter))
                {
                    throw new ArgumentException("arg is not VMOperationParameter type.");
                }
                VMOperationParameter rarg = arg as VMOperationParameter;
                if (!rarg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + rarg.ErrorMessage);
                }
                
                rarg.vmm_task_uuid = VM.async_clean_reboot(PrimarySession, rarg.vm_uuid);
                task_status_type task_status = Task.get_status(PrimarySession, rarg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("RebootGuest", e);
            }
        }

        public bool ResetVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is VMOperationParameter))
                {
                    throw new ArgumentException("arg is not VMOperationParameter type.");
                }
                VMOperationParameter rarg = arg as VMOperationParameter;
                if (!rarg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + rarg.ErrorMessage);
                }

                rarg.vmm_task_uuid = VM.async_hard_reboot(PrimarySession, rarg.vm_uuid);
                task_status_type task_status = Task.get_status(PrimarySession, rarg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("ResetVM", e);
            }
        }

        public bool PowerOffVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is VMOperationParameter))
                {
                    throw new ArgumentException("arg is not VMOperationParameter type.");
                }
                VMOperationParameter sarg = arg as VMOperationParameter;
                if (!sarg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + sarg.ErrorMessage);
                }

                sarg.vmm_task_uuid = VM.async_hard_shutdown(PrimarySession, sarg.vm_uuid);
                task_status_type task_status = Task.get_status(PrimarySession, sarg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("PowerOffVM", e);
            }
        }
        /// <summary>
        /// similar with "suspend" in vmware and "save" in hyper-v
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        public bool SuspendVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is VMOperationParameter))
                {
                    throw new ArgumentException("arg is not VMOperationParameter type.");
                }
                VMOperationParameter sarg = arg as VMOperationParameter;
                if (!sarg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + sarg.ErrorMessage);
                }

                sarg.vmm_task_uuid = VM.async_suspend(PrimarySession, sarg.vm_uuid);
                task_status_type task_status = Task.get_status(PrimarySession, sarg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("SuspendVM", e);
            }
        }

        public bool ResumeVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is StartOrResumeParameter))
                {
                    throw new ArgumentException("arg is not StartOrResumeParameter type.");
                }
                StartOrResumeParameter sarg = arg as StartOrResumeParameter;
                if (!sarg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + sarg.ErrorMessage);
                }

                sarg.vmm_task_uuid = VM.async_resume(PrimarySession, sarg.vm_uuid, sarg.paused, true);
                task_status_type task_status = Task.get_status(PrimarySession, sarg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("ResumeVM", e);
            }
        }

        public bool StandbyGuest(IVMMParameter arg)
        {
            throw new VMMNotSupportedException("StandbyGuest");
        }

        public bool CloneVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is CloneVMParameter))
                {
                    throw new ArgumentException("arg is not CloneVMParameter type.");
                }
                CloneVMParameter carg = arg as CloneVMParameter;
                if (!carg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + carg.ErrorMessage);
                }

                carg.vmm_task_uuid = VM.async_clone(PrimarySession, carg.vm_uuid, carg.vm_new_name);
                task_status_type task_status = Task.get_status(PrimarySession, carg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("CloneVM", e);
            }
        }

        public bool DeleteVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is VMOperationParameter))
                {
                    throw new ArgumentException("arg is not VMOperationParameter type.");
                }
                VMOperationParameter rarg = arg as VMOperationParameter;
                if (!rarg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + rarg.ErrorMessage);
                }

                rarg.vmm_task_uuid = VM.async_destroy(PrimarySession, rarg.vm_uuid);
                task_status_type task_status = Task.get_status(PrimarySession, rarg.vmm_task_uuid);
                return task_status == task_status_type.pending || task_status == task_status_type.success;
            }
            catch (Exception e)
            {
                throw new VMMException("DeleteVM", e);
            }
        }

        public IVMReference FindVM(IVMMParameter arg)
        {
            try
            {
                if (!(arg is FindVMParameter))
                {
                    throw new ArgumentException("arg is not FindVMParameter type.");
                }
                FindVMParameter farg = arg as FindVMParameter;
                if (!farg.IsValid())
                {
                    throw new ArgumentException("arg is not valid:" + Environment.NewLine + farg.ErrorMessage);
                }

                var vms = VM.get_by_name_label(PrimarySession, farg.vm_name);
                if (vms == null || vms.Count == 0)
                {
                    return null;
                }
                IVMReference vmObj = GetVMObject(vms[0]);
                return vmObj;
            }
            catch (Exception e)
            {
                throw new VMMException("FindVM", e);
            }
        }

        private IVMReference GetVMObject(string vm_uuid)
        {
            VM vm = VM.get_record(PrimarySession, vm_uuid);
            CitrixVMReference vmObj = new CitrixVMReference();
            vmObj.InstanceUUID = vm.uuid;
            vmObj.VMName = vm.name_label;
            vmObj.IsTemplate = vm.is_a_template;
            if (vm.power_state == vm_power_state.Running)
            {
                vmObj.PowerState = VMPowerState.PowerOn;
            }
            else if (vm.power_state == vm_power_state.Suspended)
            {
                vmObj.PowerState = VMPowerState.Suspended;
            }
            else
            {
                vmObj.PowerState = VMPowerState.PowerOff;
            }
            Host host = Host.get_record(PrimarySession, vm.resident_on);
            vmObj.HostName = host.hostname;
            VM_guest_metrics guest = VM_guest_metrics.get_record(PrimarySession, vm.guest_metrics);
            vmObj.ModifiedTime = guest.last_updated;
            VM_metrics vm_metrics = VM_metrics.get_record(PrimarySession, vm.metrics);
            vmObj.CPUCount = (int)vm_metrics.VCPUs_number;
            vmObj.MemoryMB = (int)(vm_metrics.memory_actual / Math.Pow(1024, 2));
            long diskBytes = 0;
            foreach (var vbd_ref in vm.VBDs)
            {
                VBD vbd = VBD.get_record(PrimarySession, vbd_ref);

                if (vbd.type == vbd_type.Disk)
                {
                    VDI vdi = VDI.get_record(PrimarySession, vbd.VDI);
                    diskBytes += vdi.virtual_size;
                }
            }
            vmObj.DiskGB = (int)(diskBytes / Math.Pow(1024, 3));
            vmObj.PrimaryIPv4 = vm.xenstore_data["vm-data/ip"];
            if (String.IsNullOrEmpty(vmObj.PrimaryIPv4))
            {
                if (guest.networks != null && guest.networks.ContainsKey("0/ip"))
                {
                    vmObj.PrimaryIPv4 = guest.networks["0/ip"]; //keys: 0/ip,1/ip,2/ip,...
                }
            }
            VIF vif = null;
            foreach (var vif_ref in vm.VIFs)
            {
                VIF v = VIF.get_record(PrimarySession, vif_ref);
                if (v.currently_attached)
                {
                    vif = v;
                    break;
                }
            }
            if (vif != null)
            {
                vmObj.PrimaryMac = vif.MAC;
            }

            //vmObj.GuestOsId = vm.xenstore_data["vm-data/guest/uuid"];
            //vmObj.GuestOsName = vm.xenstore_data["vm-data/guest/name"];
            //vmObj.GuestOsFamily = vm.xenstore_data["vm-data/guest/family"];
            return vmObj;
        }

        public List<IVMReference> ListVM(IVMMParameter arg)
        {
            throw new NotImplementedException();
        }

        public List<IVMReference> ListTemplateVM(IVMMParameter arg)
        {
            throw new NotImplementedException();
        }

        public bool CreateSnapshot(IVMMParameter arg)
        {
            throw new NotImplementedException();
        }

        public List<IVMSnapshotReference> ListSnapshot(IVMMParameter arg)
        {
            throw new NotImplementedException();
        }

        public bool RevertSnapshot(IVMMParameter arg)
        {
            throw new NotImplementedException();
        }

        public bool RemoveSnapshot(IVMMParameter arg)
        {
            throw new NotImplementedException();
        }

        public bool ClearSnapshot(IVMMParameter arg)
        {
            throw new NotImplementedException();
        }

        protected virtual void Disposing(bool disposed)
        {
            if (disposed)
            {
                PrimarySession.logout();
            }
        }

        public void Dispose()
        {
            Disposing(true);
        }
    }
}
